package com.jvyou.mybatis.builder.mapper;

import com.jvyou.mybatis.cache.Cache;
import com.jvyou.mybatis.exception.JvyouMybatisException;
import com.jvyou.mybatis.mapping.MappedStatement;
import com.jvyou.mybatis.mapping.SqlCommandType;
import com.jvyou.mybatis.session.Configuration;
import com.jvyou.mybatis.utils.string.StringUtils;
import com.jvyou.mybatis.xml.tag.*;
import org.dom4j.Element;
import org.dom4j.Node;

import java.util.*;

/**
 * @author 橘柚
 * @version 1.0-SNAPSHOT
 * @Date 2024/7/26 10:15
 * @Description Xml Mapper 解析器
 */
public class XmlMapperParser {

    private static final String IF_TAG = "if";
    private static final String WHERE_TAG = "where";
    private final static Map<String, Class<?>> RETURN_TYPE_MAP = new HashMap<>();

    static {
        RETURN_TYPE_MAP.put("int", int.class);
        RETURN_TYPE_MAP.put("Integer", Integer.class);
        RETURN_TYPE_MAP.put("long", long.class);
        RETURN_TYPE_MAP.put("Long", Long.class);
        RETURN_TYPE_MAP.put("float", float.class);
        RETURN_TYPE_MAP.put("Float", Float.class);
        RETURN_TYPE_MAP.put("double", double.class);
        RETURN_TYPE_MAP.put("Double", Double.class);
        RETURN_TYPE_MAP.put("boolean", boolean.class);
        RETURN_TYPE_MAP.put("Boolean", Boolean.class);
        RETURN_TYPE_MAP.put("String", String.class);
        RETURN_TYPE_MAP.put("Object", Object.class);
        RETURN_TYPE_MAP.put("void", void.class);
        RETURN_TYPE_MAP.put("Void", Void.class);
        RETURN_TYPE_MAP.put("byte", byte.class);
        RETURN_TYPE_MAP.put("Byte", Byte.class);
        RETURN_TYPE_MAP.put("short", short.class);
        RETURN_TYPE_MAP.put("Short", Short.class);
        RETURN_TYPE_MAP.put("char", char.class);
        RETURN_TYPE_MAP.put("Character", Character.class);
        RETURN_TYPE_MAP.put("Char", Character.class);
        RETURN_TYPE_MAP.put("char[]", char[].class);
        RETURN_TYPE_MAP.put("Char[]", Character[].class);
        RETURN_TYPE_MAP.put("byte[]", byte[].class);
        RETURN_TYPE_MAP.put("Byte[]", Byte[].class);
        RETURN_TYPE_MAP.put("short[]", short[].class);
        RETURN_TYPE_MAP.put("Short[]", Short[].class);
        RETURN_TYPE_MAP.put("int[]", int[].class);
        RETURN_TYPE_MAP.put("Integer[]", Integer[].class);
        RETURN_TYPE_MAP.put("long[]", long[].class);
        RETURN_TYPE_MAP.put("Long[]", Long[].class);
        RETURN_TYPE_MAP.put("float[]", float[].class);
        RETURN_TYPE_MAP.put("Float[]", Float[].class);
        RETURN_TYPE_MAP.put("double[]", double[].class);
        RETURN_TYPE_MAP.put("Double[]", Double[].class);
        RETURN_TYPE_MAP.put("boolean[]", boolean[].class);
        RETURN_TYPE_MAP.put("Boolean[]", Boolean[].class);
        RETURN_TYPE_MAP.put("String[]", String[].class);
        RETURN_TYPE_MAP.put("Object[]", Object[].class);
    }

    public MappedStatement parseMapper(final Configuration configuration, final String namespace, final Element element, final Cache cache) {

        String statementId = getStatementId(namespace, element);
        SqlNode sqlSource = parseXml(element);
        // 从 XML中读取返回值类型，可能为null
        Class<?> resultType = getReturnType(element);
        SqlCommandType sqlCommandType = getSqlCommandType(element);
        boolean selectMany = selectMany(element);

        MappedStatement mappedStatement = configuration.getMappedStatement(statementId);
        if (mappedStatement == null) {
            return MappedStatement.builder()
                    .id(statementId)
                    .sqlCommandType(sqlCommandType)
                    .cache(cache)
                    .resultType(resultType)
                    .selectMany(selectMany)
                    .sqlSource(sqlSource)
                    .build();
        }
        mappedStatement.setSqlSource(sqlSource);
        if (mappedStatement.getSqlCommandType() == null || mappedStatement.getSqlCommandType() == SqlCommandType.UNKNOWN) {
            mappedStatement.setSqlCommandType(sqlCommandType);
        }
        if (mappedStatement.getResultType() == null) {
            mappedStatement.setResultType(resultType);
        }
        if (mappedStatement.getSelectMany() == null) {
            mappedStatement.setSelectMany(selectMany);
        }
        mappedStatement.setCache(cache);
        return mappedStatement;
    }

    private String getStatementId(final String namespace, final Element element) {
        String id = element.attributeValue("id");
        if (id == null) {
            throw new JvyouMybatisException("The SQL ID of each mapper is not empty. Please check if the SQL operation under namespace " + namespace + " is missing an ID");
        }
        return namespace + "." + id;
    }

    private SqlCommandType getSqlCommandType(Element element) {
        String nodeName = element.getName();
        return SqlCommandType.valueOf(nodeName.toUpperCase());
    }

    private boolean selectMany(Element element) {
        String name = element.getName();
        String selectMany = element.attributeValue("selectMany");
        // 先检查 selectMany 是否为空，避免 trim 的时候出现 NullPointerException
        if (StringUtils.isEmpty(selectMany)) {
            return false;
        }

        return "select".equals(name) && Boolean.parseBoolean(selectMany.trim());
    }

    private Class<?> getReturnType(Element element) {
        String returnTypeString = element.attributeValue("resultType");
        if (StringUtils.isEmpty(returnTypeString)) {
            return null;
        }
        // 先从 Map 中取
        Class<?> returnType = RETURN_TYPE_MAP.get(returnTypeString);
        if (returnType != null) {
            return returnType;
        }
        try {
            returnType = Class.forName(returnTypeString);
        } catch (ClassNotFoundException e) {
            throw new JvyouMybatisException("No return value type found：" + returnTypeString);
        }
        return returnType;
    }

    @SuppressWarnings("unchecked")
    public SqlNode parseXml(Element element) {
        List<Node> childrenNodes = element.content();
        List<SqlNode> sqlNodes = new ArrayList<>();

        for (Node childNode : childrenNodes) {
            SqlNode sqlNode = null;
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                sqlNode = parseNode((Element) childNode, sqlNode);
            } else {
                sqlNode = parseTextNode(childNode);
            }

            Optional.ofNullable(sqlNode).ifPresent(sqlNodes::add);
        }
        return new MixedSqlNode(sqlNodes);
    }

    private SqlNode parseNode(Element childNode, SqlNode sqlNode) {
        String nodeName = childNode.getName();
        switch (nodeName) {
            case IF_TAG:
                sqlNode = parseIfNode(childNode);
                break;
            case WHERE_TAG:
                sqlNode = new WhereSqlNode(parseXml(childNode));
                break;
        }
        return sqlNode;
    }

    private SqlNode parseIfNode(Element element) {
        String test = element.attributeValue("test");
        if (test == null) {
            throw new RuntimeException("if标签的test属性不能为空");
        }
        return new IfSqlNode(test, parseXml(element));
    }

    private SqlNode parseTextNode(Node node) {
        String sql = node.getText().trim();
        if (sql.length() == 0) {
            return null;
        }
        if (sql.contains("$")) {
            return new TextSqlNode(sql);
        } else {
            return new StaticTextSqlNode(sql);
        }
    }
}
